﻿// Gateway.Api 项目的版权、商标、专利和其他相关权利均受相应法律法规的保护。使用本项目应遵守相关法律法规和许可证的要求。
//
// 本项目主要遵循 MIT 许可证和 Apache 许可证（版本 2.0）进行分发和使用。许可证位于源代码树根目录中的 LICENSE-MIT 和 LICENSE-APACHE 文件。
//
// 不得利用本项目从事危害国家安全、扰乱社会秩序、侵犯他人合法权益等法律法规禁止的活动！任何基于本项目二次开发而产生的一切法律纠纷和责任，我们不承担任何责任！

using Aurora.AI.Common.Constants;
using Aurora.AI.Common.Extensions;
using Aurora.AI.Common.Models;
using Aurora.AI.Common.Options;
using Aurora.AI.Wiki;
using Aurora.AI.Wiki.Domain.Domain.Wikis.Aggregates;
using Aurora.AI.Wiki.Services;
using Aurora.AI.Wiki.Services.Wiki;
using Aurora.SingalR.Hubs;
using Aurora.TaskScheduler.Abstractions;
using Aurora.TaskScheduler.Abstractions.BaseModel;
using FileServer.Abstractions;
using FileServer.Extension;
using Gateway.Common.Enums;
using Gateway.UI.Api.AiServices.Wiki.KM;
using mem0.Core;
using Microsoft.Extensions.Options;
using Microsoft.KernelMemory;
using Microsoft.KernelMemory.Handlers;
using Microsoft.SemanticKernel.Text;

namespace Gateway.UI.Api.TaskJobs;

/// <summary>
/// wiki detail job
/// </summary>
public class WikiDetailJob(
    ILogger<WikiDetailJob> logger,
    WikiMemoryService wikiMemoryService,
    WikiService wikiService,
    WikiDetailService wikiDetailService,
    WikiSettingService wikiSettingService,
    IFileStorage fileStorage,
    IRedisCache redisCache,
    IOptions<FileServer.Abstractions.Models.Options.FileServerOptions> options,
    HubHandlerProvider hubHandlerProvider,
    IServiceProvider serviceProvider) : ITaskJob<WikiDetail>
{
    /// <summary>
    /// 执行脚本
    /// </summary>
    /// <param name="parameter"></param>
    /// <returns></returns>
    public async Task<JobResult> ExecuteAsync(WikiDetail parameter)
    {
        // 加锁
        logger.LogInformation("当前执行任务开始");
        var wiki = await wikiService.GetByIdAsync(parameter.WikiId);
        if (parameter.Mode == ProcessMode.Auto)
        {
            parameter.MaxTokensPerLine = 300;
            parameter.MaxTokensPerParagraph = 1000;
            parameter.OverlappingTokens = 100;
        }

        string quantizationId = await wikiDetailService.CreateQuantizationListAsync(parameter.WikiId, parameter.Id,
            $"创建量化任务：{parameter.FileName} {parameter.Path} {parameter.FileId}");
        var commonSetting = await wikiSettingService.GetCommonSetting();

        try
        {
            // mem0
            if (wiki.VectorType.Equals(VectorType.Mem0))
            {
                await HandleVectorMem0Async(parameter, wiki, quantizationId);
            }
            else
            {
                await HandleVectorNoneAsync(parameter, wiki, commonSetting, quantizationId);
            }
        }
        catch (Exception e)
        {
            logger.LogError(e, $"量化失败{parameter.FileName} {parameter.Path} {parameter.FileId}");
            await hubHandlerProvider.Send(new HubMessage()
            {
                Type = SendTypeEnum.Person.Value,
                Message = $"量化失败:{parameter.FileName}",
                User = parameter.Creator,
                Creator = parameter.Creator
            });
            await wikiDetailService.CompleteQuantizationListAsync(quantizationId,
                $"量化失败：{parameter.FileName} {parameter.Path} {parameter.FileId} {e.Message}",
                3);
            if (parameter.State != 2)
                await wikiDetailService.UpdateDetailsState(parameter.Id, 2);
        }
        finally
        {
            redisCache.Del("Wiki-detail:" + parameter.Id);
            redisCache.Del("Wiki:" + parameter.Id);
        }

        return JobResult.CreateSuccess();
    }

    /// <summary>
    /// 处理 Mem0 类型的
    /// </summary>
    private async Task HandleVectorMem0Async(WikiDetail parameter, Wiki wiki, string quantizationId)
    {
        logger.LogInformation($"[VectorMem0]开始量化：{parameter.FileName} {parameter.Path} {parameter.FileId}");
        mem0.NET.Services.MemoryService memoryService = serviceProvider.GetRequiredService<mem0.NET.Services.MemoryService>();
        ApplicationContext.HistoryTrackId.Value = parameter.Id;
        var result = string.Empty;
        if (parameter.Type.Equals("file"))
        {
            var fileInfo = await fileStorage.Get(parameter.FileId);
            string fullPath = FileUploadProvider.GetLocalFullPath(fileInfo.Path, options.Value);
            string files = await File.ReadAllTextAsync(fullPath);
#pragma warning disable SKEXP0001, SKEXP0010, SKEXP0050,SKEXP0020 
            var fileContents = TextChunker.SplitMarkdownParagraphs([files], parameter.MaxTokensPerParagraph!.Value,
                parameter.OverlappingTokens!.Value);
            foreach (string item in fileContents)
            {
                ApplicationContext.AddMemoryMetadata.Value = new Dictionary<string, string>
                {
                    { "wikiId", parameter.WikiId },
                    { "user_id", wiki.Creator },
                    { "run_id", parameter.Id },
                    { "agent_id", wiki.Id },
                    { "wikiDetailId", parameter.Id },
                    { "fileIds", fileInfo.Id },
                    { "metaData", item }
                };
                await memoryService.CreateMemoryAsync(new CreateMemoryInput()
                {
                    Data = item,
                    UserId = wiki.Creator,
                    RunId = parameter.Id,
                    AgentId = wiki.Id,
                    Prompt = Mem0Prompt.Replace("{user_input}", item)
                        .Replace("{metadata}", AuroraAIJsonSerializer.Serialize(new { }))
                });
            }
        }
        else if (parameter.Type == "data")
        {
            var fileContents = TextChunker.SplitMarkdownParagraphs([parameter.Path],
                parameter.MaxTokensPerParagraph!.Value,
                parameter.OverlappingTokens!.Value);

            foreach (var item in fileContents)
            {
                ApplicationContext.AddMemoryMetadata.Value = new Dictionary<string, string>
                {
                    { "wikiId", parameter.WikiId },
                    { "wikiDetailId", parameter.Id },
                    { "metaData", item }
                };

                await memoryService.CreateMemoryAsync(new CreateMemoryInput()
                {
                    Data = item,
                    UserId = wiki.Creator,
                    RunId = parameter.Id,
                    AgentId = wiki.Id,
                });
            }
        }
        
        await wikiDetailService.UpdateDetailsState(parameter.Id, 1);

        await wikiDetailService.CompleteQuantizationListAsync(quantizationId,
            $"量化成功：{parameter.FileName} {parameter.Path} {parameter.FileId} {result}",
            2);
        logger.LogInformation($"[VectorMem0]量化成功：{parameter.FileName} {parameter.Path} {parameter.FileId} {result}");
        await hubHandlerProvider.Send(new HubMessage()
        {
            Type = SendTypeEnum.Person.Value,
            Message = $"量化成功:{parameter.FileName}",
            User = parameter.Creator,
            Creator = parameter.Creator
        });
    }

    /// <summary>
    /// 处理 默认类型的
    /// </summary>
    /// <param name="parameter"></param>
    /// <param name="wiki"></param>
    /// <param name="commonSetting"></param>
    /// <param name="quantizationId"></param>
    private async Task HandleVectorNoneAsync(WikiDetail parameter, Wiki wiki, AICommonSetting commonSetting, string quantizationId)
    {
        // 获取知识库的配置的模型，如果没有则使用默认模型
        var serverless = wikiMemoryService.CreateMemoryServerless(new Microsoft.KernelMemory.SearchClientConfig(),
            parameter.MaxTokensPerLine.Value, parameter.MaxTokensPerParagraph.Value, parameter.OverlappingTokens.Value,
            wiki.Model, wiki.EmbeddingModel, commonSetting, redisCache, wikiSettingService);
        logger.LogInformation($"[VectorNone]开始量化：{parameter.FileName} {parameter.Path} {parameter.FileId}");
        List<string> step = [];
        if (parameter.TrainingPattern == 1)
        {
            var stepName = parameter.Id;
            await redisCache.AddAsync("Wiki:" + stepName, wiki, TimeSpan.FromHours(2));
            await redisCache.AddAsync("Wiki-detail:" + stepName, parameter, TimeSpan.FromHours(2));
            serverless.Orchestrator.AddHandler<TextExtractionHandler>("extract_text");
            serverless.Orchestrator.AddHandler<QAHandler>(stepName);
            serverless.Orchestrator.AddHandler<GenerateEmbeddingsHandler>("generate_embeddings");
            serverless.Orchestrator.AddHandler<SaveRecordsHandler>("save_memory_records");
            step.Add("extract_text");
            step.Add(stepName);
            step.Add("generate_embeddings");
            step.Add("save_memory_records");
        }

        var result = string.Empty;

        if (parameter.Type == "file")
        {
            // 根据id 查询 文件信息
            var fileInfo = await fileStorage.Get(parameter.FileId);
            string fullPath = FileUploadProvider.GetLocalFullPath(fileInfo.Path, options.Value);
            result = await serverless.ImportDocumentAsync(fullPath, parameter.Id,
                new TagCollection
                {
                    {
                        "wikiId", parameter.WikiId
                    },
                    {
                        "fileId", parameter.FileId
                    },
                    {
                        "wikiDetailId", parameter.Id
                    }
                }, OpenAIOption.WikiVectorCollectionName, step.ToArray());
        }
        else if (parameter.Type == "web")
        {
            result = await serverless.ImportWebPageAsync(parameter.Path,
                parameter.Id,
                new TagCollection
                {
                    {
                        "wikiId", parameter.WikiId
                    },
                    {
                        "wikiDetailId", parameter.Id
                    }
                }, OpenAIOption.WikiVectorCollectionName, step.ToArray());
        }
        else if (parameter.Type == "data")
        {
            var fileInfo = await fileStorage.Get(parameter.FileId);
            string fullPath = FileUploadProvider.GetLocalFullPath(fileInfo.Path, options.Value);
            result = await serverless.ImportDocumentAsync(fullPath,
                parameter.Id,
                new TagCollection
                {
                    {
                        "wikiId", parameter.WikiId
                    },
                    {
                        "wikiDetailId", parameter.Id
                    }
                }, OpenAIOption.WikiVectorCollectionName, step.ToArray());
        }

        await wikiDetailService.UpdateDetailsState(parameter.Id, 1);

        await wikiDetailService.CompleteQuantizationListAsync(quantizationId,
            $"量化成功：{parameter.FileName} {parameter.Path} {parameter.FileId} {result}",
            2);
        logger.LogInformation($"[VectorNone]量化成功：{parameter.FileName} {parameter.Path} {parameter.FileId} {result}");
        await hubHandlerProvider.Send(new HubMessage()
        {
            Type = SendTypeEnum.Person.Value,
            Message = $"量化成功:{parameter.FileName}",
            User = parameter.Creator,
            Creator = parameter.Creator
        });
    }


    private const string Mem0Prompt = """
                                      推断出提供文本中的事实、偏好和记忆。
                                      只需以要点形式返回事实、偏好和记忆。:
                                      自然语言文本: {user_input}
                                      User/Agent details: {metadata}

                                      推导事实、偏好和记忆的约束：
                                      - 事实、偏好和记忆应简洁明了，但是需要含有重要信息。
                                      - 不要以“这个人喜欢披萨”开头。相反，从“喜欢披萨”开始。
                                      - 不要记住所提供的User/Agent详细信息。只记住事实、偏好和回忆。
                                      推导出的事实、偏好和记忆:

                                      """;
}